Mestr TypeScripts utility-typer: kraftfulde værktøjer til typetransformation, som forbedrer kodens genbrugelighed og typesikkerhed i dine applikationer.
TypeScript Utility Types: Indbyggede værktøjer til typemanipulation
TypeScript er et kraftfuldt sprog, der bringer statisk typning til JavaScript. En af dets nøglefunktioner er evnen til at manipulere typer, hvilket giver udviklere mulighed for at skabe mere robust og vedligeholdelsesvenlig kode. TypeScript leverer et sæt indbyggede utility-typer, der forenkler almindelige typetransformationer. Disse utility-typer er uvurderlige værktøjer til at forbedre typesikkerhed, øge genbrugelighed af kode og strømline din udviklingsproces. Denne omfattende guide udforsker de mest essentielle TypeScript utility-typer og giver praktiske eksempler og handlingsorienterede indsigter for at hjælpe dig med at mestre dem.
Hvad er TypeScript Utility Types?
Utility-typer er foruddefinerede typeoperatorer, der transformerer eksisterende typer til nye typer. De er indbygget i TypeScript-sproget og giver en kortfattet og deklarativ måde at udføre almindelige typemanipulationer på. Brug af utility-typer kan betydeligt reducere "boilerplate"-kode og gøre dine typedefinitioner mere udtryksfulde og lettere at forstå.
Tænk på dem som funktioner, der opererer på typer i stedet for værdier. De tager en type som input og returnerer en modificeret type som output. Dette giver dig mulighed for at skabe komplekse typeforhold og transformationer med minimal kode.
Hvorfor bruge Utility Types?
Der er flere overbevisende grunde til at indarbejde utility-typer i dine TypeScript-projekter:
- Øget typesikkerhed: Utility-typer hjælper dig med at håndhæve strengere typebegrænsninger, hvilket reducerer sandsynligheden for runtime-fejl og forbedrer den overordnede pålidelighed af din kode.
- Forbedret genbrugelighed af kode: Ved at bruge utility-typer kan du skabe generiske komponenter og funktioner, der fungerer med en række forskellige typer, hvilket fremmer genbrug af kode og reducerer redundans.
- Mindre "boilerplate"-kode: Utility-typer giver en kortfattet og deklarativ måde at udføre almindelige typetransformationer på, hvilket reducerer mængden af "boilerplate"-kode, du skal skrive.
- Forbedret læsbarhed: Utility-typer gør dine typedefinitioner mere udtryksfulde og lettere at forstå, hvilket forbedrer læsbarheden og vedligeholdelsen af din kode.
Essentielle TypeScript Utility Types
Lad os udforske nogle af de mest almindeligt anvendte og fordelagtige utility-typer i TypeScript. Vi vil dække deres formål, syntaks og give praktiske eksempler for at illustrere deres anvendelse.
1. Partial<T>
Partial<T>
utility-typen gør alle egenskaber i typen T
valgfrie. Dette er nyttigt, når du vil oprette en ny type, der har nogle eller alle egenskaberne fra en eksisterende type, men du ikke ønsker at kræve, at de alle er til stede.
Syntaks:
type Partial<T> = { [P in keyof T]?: T[P]; };
Eksempel:
interface User {
id: number;
name: string;
email: string;
}
type OptionalUser = Partial<User>; // Alle egenskaber er nu valgfrie
const partialUser: OptionalUser = {
name: "Alice", // Angiver kun 'name'-egenskaben
};
Brugsscenarie: Opdatering af et objekt med kun bestemte egenskaber. Forestil dig for eksempel en formular til opdatering af en brugerprofil. Du ønsker ikke at kræve, at brugerne opdaterer alle felter på én gang.
2. Required<T>
Required<T>
utility-typen gør alle egenskaber i typen T
påkrævede. Det er det modsatte af Partial<T>
. Dette er nyttigt, når du har en type med valgfrie egenskaber, og du vil sikre, at alle egenskaber er til stede.
Syntaks:
type Required<T> = { [P in keyof T]-?: T[P]; };
Eksempel:
interface Config {
apiKey?: string;
apiUrl?: string;
}
type CompleteConfig = Required<Config>; // Alle egenskaber er nu påkrævede
const config: CompleteConfig = {
apiKey: "your-api-key",
apiUrl: "https://example.com/api",
};
Brugsscenarie: Håndhævelse af, at alle konfigurationsindstillinger er angivet, før en applikation startes. Dette kan hjælpe med at forhindre runtime-fejl forårsaget af manglende eller udefinerede indstillinger.
3. Readonly<T>
Readonly<T>
utility-typen gør alle egenskaber i typen T
skrivebeskyttede (readonly). Dette forhindrer dig i ved et uheld at ændre egenskaberne for et objekt, efter det er blevet oprettet. Dette fremmer uforanderlighed (immutability) og forbedrer forudsigeligheden af din kode.
Syntaks:
type Readonly<T> = { readonly [P in keyof T]: T[P]; };
Eksempel:
interface Product {
id: number;
name: string;
price: number;
}
type ImmutableProduct = Readonly<Product>; // Alle egenskaber er nu skrivebeskyttede
const product: ImmutableProduct = {
id: 123,
name: "Example Product",
price: 25.99,
};
// product.price = 29.99; // Fejl: Kan ikke tildele til 'price', da det er en skrivebeskyttet egenskab.
Brugsscenarie: Oprettelse af uforanderlige datastrukturer, såsom konfigurationsobjekter eller dataoverførselsobjekter (DTO'er), der ikke bør ændres efter oprettelse. Dette er især nyttigt i funktionelle programmeringsparadigmer.
4. Pick<T, K extends keyof T>
Pick<T, K extends keyof T>
utility-typen opretter en ny type ved at vælge et sæt egenskaber K
fra typen T
. Dette er nyttigt, når du kun har brug for en delmængde af egenskaberne fra en eksisterende type.
Syntaks:
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
Eksempel:
interface Employee {
id: number;
name: string;
department: string;
salary: number;
}
type EmployeeNameAndDepartment = Pick<Employee, "name" | "department">; // Vælg kun 'name' og 'department'
const employeeInfo: EmployeeNameAndDepartment = {
name: "Bob",
department: "Engineering",
};
Brugsscenarie: Oprettelse af specialiserede dataoverførselsobjekter (DTO'er), der kun indeholder de nødvendige data til en bestemt operation. Dette kan forbedre ydeevnen og reducere mængden af data, der overføres over netværket. Forestil dig at sende brugeroplysninger til klienten, men udelade følsomme oplysninger som løn. Du kan bruge Pick til kun at sende `id` og `name`.
5. Omit<T, K extends keyof any>
Omit<T, K extends keyof any>
utility-typen opretter en ny type ved at udelade et sæt egenskaber K
fra typen T
. Dette er det modsatte af Pick<T, K extends keyof T>
og er nyttigt, når du vil ekskludere bestemte egenskaber fra en eksisterende type.
Syntaks:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Eksempel:
interface Event {
id: number;
title: string;
description: string;
date: Date;
location: string;
}
type EventSummary = Omit<Event, "description" | "location">; // Udelad 'description' og 'location'
const eventPreview: EventSummary = {
id: 1,
title: "Conference",
date: new Date(),
};
Brugsscenarie: Oprettelse af forenklede versioner af datamodeller til specifikke formål, såsom at vise en oversigt over en begivenhed uden at inkludere den fulde beskrivelse og placering. Dette kan også bruges til at fjerne følsomme felter, før data sendes til en klient.
6. Exclude<T, U>
Exclude<T, U>
utility-typen opretter en ny type ved at ekskludere alle typer fra T
, der kan tildeles til U
. Dette er nyttigt, når du vil fjerne bestemte typer fra en union-type.
Syntaks:
type Exclude<T, U> = T extends U ? never : T;
Eksempel:
type AllowedFileTypes = "image" | "video" | "audio" | "document";
type MediaFileTypes = "image" | "video" | "audio";
type DocumentFileTypes = Exclude<AllowedFileTypes, MediaFileTypes>; // "document"
const fileType: DocumentFileTypes = "document";
Brugsscenarie: Filtrering af en union-type for at fjerne specifikke typer, der ikke er relevante i en given kontekst. For eksempel vil du måske ekskludere bestemte filtyper fra en liste over tilladte filtyper.
7. Extract<T, U>
Extract<T, U>
utility-typen opretter en ny type ved at udtrække alle typer fra T
, der kan tildeles til U
. Dette er det modsatte af Exclude<T, U>
og er nyttigt, når du vil vælge specifikke typer fra en union-type.
Syntaks:
type Extract<T, U> = T extends U ? T : never;
Eksempel:
type InputTypes = string | number | boolean | null | undefined;
type PrimitiveTypes = string | number | boolean;
type NonNullablePrimitives = Extract<InputTypes, PrimitiveTypes>; // string | number | boolean
const value: NonNullablePrimitives = "hello";
Brugsscenarie: Valg af specifikke typer fra en union-type baseret på bestemte kriterier. For eksempel vil du måske udtrække alle primitive typer fra en union-type, der inkluderer både primitive typer og objekttyper.
8. NonNullable<T>
NonNullable<T>
utility-typen opretter en ny type ved at ekskludere null
og undefined
fra typen T
. Dette er nyttigt, når du vil sikre, at en type ikke kan være null
eller undefined
.
Syntaks:
type NonNullable<T> = T extends null | undefined ? never : T;
Eksempel:
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>; // string
const message: DefinitelyString = "Hello, world!";
Brugsscenarie: Håndhævelse af, at en værdi ikke er null
eller undefined
, før der udføres en operation på den. Dette kan hjælpe med at forhindre runtime-fejl forårsaget af uventede null- eller undefined-værdier. Overvej et scenarie, hvor du skal behandle en brugers adresse, og det er afgørende, at adressen ikke er null før nogen operation.
9. ReturnType<T extends (...args: any) => any>
ReturnType<T extends (...args: any) => any>
utility-typen udtrækker returtypen for en funktionstype T
. Dette er nyttigt, når du vil kende typen af den værdi, som en funktion returnerer.
Syntaks:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
Eksempel:
function fetchData(url: string): Promise<{ data: any }> {
return fetch(url).then(response => response.json());
}
type FetchDataReturnType = ReturnType<typeof fetchData>; // Promise<{ data: any }>
async function processData(data: FetchDataReturnType) {
// ...
}
Brugsscenarie: Bestemmelse af typen af den værdi, der returneres af en funktion, især når man arbejder med asynkrone operationer eller komplekse funktionssignaturer. Dette giver dig mulighed for at sikre, at du håndterer den returnerede værdi korrekt.
10. Parameters<T extends (...args: any) => any>
Parameters<T extends (...args: any) => any>
utility-typen udtrækker parametertyperne for en funktionstype T
som en tuple. Dette er nyttigt, når du vil kende typerne af de argumenter, som en funktion accepterer.
Syntaks:
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
Eksempel:
function createUser(name: string, age: number, email: string): void {
// ...
}
type CreateUserParams = Parameters<typeof createUser>; // [string, number, string]
function logUser(...args: CreateUserParams) {
console.log("Creating user with:", args);
}
Brugsscenarie: Bestemmelse af typerne af de argumenter, som en funktion accepterer, hvilket kan være nyttigt til at skabe generiske funktioner eller decorators, der skal fungere med funktioner med forskellige signaturer. Det hjælper med at sikre typesikkerhed, når argumenter sendes dynamisk til en funktion.
11. ConstructorParameters<T extends abstract new (...args: any) => any>
ConstructorParameters<T extends abstract new (...args: any) => any>
utility-typen udtrækker parametertyperne for en constructor-funktionstype T
som en tuple. Dette er nyttigt, når du vil kende typerne af de argumenter, som en constructor accepterer.
Syntaks:
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;
Eksempel:
class Logger {
constructor(public prefix: string, public enabled: boolean) {}
log(message: string) {
if (this.enabled) {
console.log(`${this.prefix}: ${message}`);
}
}
}
type LoggerConstructorParams = ConstructorParameters<typeof Logger>; // [string, boolean]
function createLogger(...args: LoggerConstructorParams) {
return new Logger(...args);
}
Brugsscenarie: Ligner Parameters
, men specifikt for constructor-funktioner. Det hjælper, når man opretter factories eller dependency injection-systemer, hvor man dynamisk skal instantiere klasser med forskellige constructor-signaturer.
12. InstanceType<T extends abstract new (...args: any) => any>
InstanceType<T extends abstract new (...args: any) => any>
utility-typen udtrækker instanstypen for en constructor-funktionstype T
. Dette er nyttigt, når du vil kende typen af det objekt, som en constructor opretter.
Syntaks:
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;
Eksempel:
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
type GreeterInstance = InstanceType<typeof Greeter>; // Greeter
const myGreeter: GreeterInstance = new Greeter("World");
console.log(myGreeter.greet());
Brugsscenarie: Bestemmelse af typen af det objekt, der er oprettet af en constructor, hvilket er nyttigt, når man arbejder med nedarvning eller polymorfi. Det giver en typesikker måde at henvise til instansen af en klasse på.
13. Record<K extends keyof any, T>
Record<K extends keyof any, T>
utility-typen konstruerer en objekttype, hvis egenskabsnøgler er K
, og hvis egenskabsværdier er T
. Dette er nyttigt til at oprette ordbogslignende typer, hvor du kender nøglerne på forhånd.
Syntaks:
type Record<K extends keyof any, T> = { [P in K]: T; };
Eksempel:
type CountryCode = "US" | "CA" | "GB" | "DE";
type CurrencyMap = Record<CountryCode, string>; // { US: string; CA: string; GB: string; DE: string; }
const currencies: CurrencyMap = {
US: "USD",
CA: "CAD",
GB: "GBP",
DE: "EUR",
};
Brugsscenarie: Oprettelse af ordbogslignende objekter, hvor du har et fast sæt nøgler og vil sikre, at alle nøgler har værdier af en bestemt type. Dette er almindeligt, når man arbejder med konfigurationsfiler, datamappings eller opslagstabeller.
Brugerdefinerede Utility Types
Selvom TypeScripts indbyggede utility-typer er kraftfulde, kan du også oprette dine egne brugerdefinerede utility-typer for at imødekomme specifikke behov i dine projekter. Dette giver dig mulighed for at indkapsle komplekse typetransformationer og genbruge dem i hele din kodebase.
Eksempel:
// En utility-type til at hente nøglerne fra et objekt, der har en bestemt type
type KeysOfType<T, U> = { [K in keyof T]: T[K] extends U ? K : never }[keyof T];
interface Person {
name: string;
age: number;
address: string;
phoneNumber: number;
}
type StringKeys = KeysOfType<Person, string>; // "name" | "address"
Bedste praksis for brug af Utility Types
- Brug beskrivende navne: Giv dine utility-typer meningsfulde navne, der tydeligt angiver deres formål. Dette forbedrer læsbarheden og vedligeholdelsen af din kode.
- Dokumentér dine utility-typer: Tilføj kommentarer for at forklare, hvad dine utility-typer gør, og hvordan de skal bruges. Dette hjælper andre udviklere med at forstå din kode og bruge den korrekt.
- Hold det simpelt: Undgå at oprette alt for komplekse utility-typer, der er svære at forstå. Opdel komplekse transformationer i mindre, mere håndterbare utility-typer.
- Test dine utility-typer: Skriv enhedstests for at sikre, at dine utility-typer fungerer korrekt. Dette hjælper med at forhindre uventede fejl og sikrer, at dine typer opfører sig som forventet.
- Overvej ydeevne: Selvom utility-typer generelt ikke har en betydelig indvirkning på ydeevnen, skal du være opmærksom på kompleksiteten af dine typetransformationer, især i store projekter.
Konklusion
TypeScript utility-typer er kraftfulde værktøjer, der kan forbedre typesikkerheden, genbrugeligheden og vedligeholdelsen af din kode betydeligt. Ved at mestre disse utility-typer kan du skrive mere robuste og udtryksfulde TypeScript-applikationer. Denne guide har dækket de mest essentielle TypeScript utility-typer og givet praktiske eksempler og handlingsorienterede indsigter for at hjælpe dig med at indarbejde dem i dine projekter.
Husk at eksperimentere med disse utility-typer og udforske, hvordan de kan bruges til at løse specifikke problemer i din egen kode. Efterhånden som du bliver mere fortrolig med dem, vil du opdage, at du bruger dem mere og mere til at skabe renere, mere vedligeholdelsesvenlige og mere typesikre TypeScript-applikationer. Uanset om du bygger webapplikationer, server-side applikationer eller noget derimellem, giver utility-typer et værdifuldt sæt værktøjer til at forbedre din udviklingsproces og kvaliteten af din kode. Ved at udnytte disse indbyggede værktøjer til typemanipulation kan du frigøre det fulde potentiale i TypeScript og skrive kode, der er både udtryksfuld og robust.